package com.jy.rhin.infra.repo.document.submission;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.jy.common.UIDWorker;
import com.jy.rhin.domain.document.submission.DocumentSubmissionDao;
import com.jy.rhin.domain.document.submission.entity.*;
import com.jy.rhin.infra.repo.document.submission.mapper.*;
import com.jy.rhin.infra.repo.document.submission.po.*;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.jy.rhin.infra.uitl.LambdaPropertyUtils.getPropertyName;

/**
 * @author dhl
 * @date 2022/6/1
 * @Description
 */
@Service
public class DocumentSubmissionDaoImpl implements DocumentSubmissionDao {

    @Resource
    private DocumentAssociationMapper documentAssociationMapper;

    @Resource
    private DocumentEntryMapper documentEntryMapper;

    @Resource
    private DocumentFolderMapper documentFolderMapper;

    @Resource
    private DocumentSubmissionSetMapper documentSubmissionSetMapper;

    @Resource
    private RhinCodingMapper rhinCodingMapper;
    @Resource
    UIDWorker uidWorker;

    @Override
    public Long save(DocumentSubmissionDO submission) {
        long submissionId = uidWorker.nextId();
        submission.setId(submissionId);
        DocumentSubmissionSetPO po = new DocumentSubmissionSetPO(submission);
        saveEntry(submission, submissionId);
        saveFolder(submission, submissionId);
        saveAssociation(submission, submissionId);
        saveRhinCoding(submission, DocumentSubmissionDO::getId, DocumentSubmissionDO::getContentTypeCode);
        documentSubmissionSetMapper.insert(po);
        return submissionId;
    }

    private void saveEntry(DocumentSubmissionDO submissionDO, long submissionId) {
        List<DocumentEntryVO> entries = submissionDO.getEntries();
        if (CollectionUtils.isEmpty(entries)) {
            return;
        }
        for (DocumentEntryVO entryVO : entries) {
            entryVO.setId(uidWorker.nextId());
            documentEntryMapper.insert(new DocumentEntryPO(submissionId, entryVO));
            saveRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getTypeCode);
            saveRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getClassCode);
            saveRhinCodingBatch(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getEventCode);
            saveRhinCodingSingle(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getConfidentialityCode);
            saveRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getPracticeSettingCode);
            saveRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getHealthcareFacilityTypeCode);
        }
    }

    private void saveFolder(DocumentSubmissionDO dto, long submissionId) {
        List<DocumentFolderVO> folders = dto.getFolders();
        if (CollectionUtils.isEmpty(folders)) {
            return;
        }
        for (DocumentFolderVO folderVO : folders) {
            folderVO.setId(uidWorker.nextId());
            documentFolderMapper.insert(new DocumentFolderPO(submissionId, folderVO));
            saveRhinCodingBatch(folderVO, DocumentFolderVO::getId, DocumentFolderVO::getTypeCode);
        }
    }

    private void saveAssociation(DocumentSubmissionDO dto, long submissionId) {
        List<DocumentAssociationVO> associations = dto.getAssociations();
        if (CollectionUtils.isEmpty(associations)) {
            return;
        }
        for (DocumentAssociationVO associationVO : associations) {
            associationVO.setId(uidWorker.nextId());
            documentAssociationMapper.insert(new DocumentAssociationPO(submissionId, associationVO));
        }
    }

    private <T> void saveRhinCoding(T entity, SFunction<T, Long> idGetter, SFunction<T, RhinCodeableVO> rhinCodingGetter) {
        String module = getPropertyName(entity, rhinCodingGetter);
        RhinCodeableVO rhinCodeableVO = rhinCodingGetter.apply(entity);
        saveRhinCodingList(rhinCodeableVO, idGetter.apply(entity), module);
    }

    private <T> void saveRhinCodingBatch(T entity, SFunction<T, Long> idGetter, SFunction<T, List<RhinCodeableVO>> rhinCodingGetter) {
        String module = getPropertyName(entity, rhinCodingGetter);
        List<RhinCodeableVO> voList = rhinCodingGetter.apply(entity);
        for (RhinCodeableVO rhinCodeableVO : voList) {
            saveRhinCodingList(rhinCodeableVO, idGetter.apply(entity), module);
        }
    }

    private <T> void saveRhinCodingSingle(T entity, SFunction<T, Long> idGetter, SFunction<T, RhinCodingVO> rhinCodingGetter) {
        String module = getPropertyName(entity, rhinCodingGetter);
        RhinCodingVO rhinCodingVO = rhinCodingGetter.apply(entity);
        Long moduleId = idGetter.apply(entity);
        if (Objects.nonNull(rhinCodingVO)) {
            RhinCodingPO po = new RhinCodingPO(moduleId, module, rhinCodingVO);
            po.setId(uidWorker.nextId());
            rhinCodingMapper.insert(po);
        }
    }

    private void saveRhinCodingList(RhinCodeableVO rhinCodeableVO, Long moduleId, String module) {
        List<RhinCodingVO> rhinCodingList = rhinCodeableVO.getCoding();
        if (!CollectionUtils.isEmpty(rhinCodingList)) {
            for (RhinCodingVO rhinCodingVO : rhinCodingList) {
                RhinCodingPO po = new RhinCodingPO(moduleId, module, rhinCodingVO);
                po.setId(uidWorker.nextId());
                rhinCodingMapper.insert(po);
            }
        }
    }

    @Override
    public Long update(DocumentSubmissionDO submission) {
        QueryWrapper<DocumentSubmissionSetPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(DocumentSubmissionSetPO::getUniqueId, submission.getUniqueId());
        DocumentSubmissionSetPO documentSubmissionSetPO = documentSubmissionSetMapper.selectOne(queryWrapper);
        if (documentSubmissionSetPO == null) {
            return null;
        }

        long submissionId = documentSubmissionSetPO.getId();
        submission.setId(submissionId);
        DocumentSubmissionSetPO po = new DocumentSubmissionSetPO(submission);
        updateEntry(submission, submissionId);
        updateFolder(submission, submissionId);
        updateAssociation(submission, submissionId);
        updateRhinCoding(submission, DocumentSubmissionDO::getId, DocumentSubmissionDO::getContentTypeCode);
        documentSubmissionSetMapper.updateById(po);
        return submissionId;
    }

    private void updateEntry(DocumentSubmissionDO submissionDO, long submissionId) {
        List<DocumentEntryVO> entries = submissionDO.getEntries();
        if (CollectionUtils.isEmpty(entries)) {
            return;
        }

        QueryWrapper<DocumentEntryPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(DocumentEntryPO::getSubmissionId, submissionId);
        List<DocumentEntryPO> list = documentEntryMapper.selectList(queryWrapper);

        for (DocumentEntryVO entryVO : entries) {
            DocumentEntryPO po = list.stream().filter(o -> o.getId().equals(entryVO.getId())).findFirst().orElse(null);
            if (po != null) {
                documentEntryMapper.updateById(new DocumentEntryPO(submissionId, entryVO));
            } else {
                entryVO.setId(uidWorker.nextId());
                documentEntryMapper.insert(new DocumentEntryPO(submissionId, entryVO));
            }

            updateRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getTypeCode);
            updateRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getClassCode);
            updateRhinCodingBatch(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getEventCode);
            updateRhinCodingSingle(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getConfidentialityCode);
            updateRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getPracticeSettingCode);
            updateRhinCoding(entryVO, DocumentEntryVO::getId, DocumentEntryVO::getHealthcareFacilityTypeCode);
        }

        List<Long> ids = entries.stream().map(DocumentEntryVO::getId).collect(Collectors.toList());
        List<Long> removeIds = list.stream().filter(o -> !ids.contains(o.getId())).map(DocumentEntryPO::getId).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(removeIds)) {
            documentEntryMapper.deleteBatchIds(removeIds);
        }
    }

    private void updateFolder(DocumentSubmissionDO dto, long submissionId) {
        List<DocumentFolderVO> folders = dto.getFolders();
        if (CollectionUtils.isEmpty(folders)) {
            return;
        }

        QueryWrapper<DocumentFolderPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(DocumentFolderPO::getSubmissionId, submissionId);
        List<DocumentFolderPO> list = documentFolderMapper.selectList(queryWrapper);

        for (DocumentFolderVO folderVO : folders) {
            DocumentFolderPO po = list.stream().filter(o -> o.getId().equals(folderVO.getId())).findFirst().orElse(null);
            if (po != null) {
                documentFolderMapper.updateById(new DocumentFolderPO(submissionId, folderVO));
            } else {
                folderVO.setId(uidWorker.nextId());
                documentFolderMapper.insert(new DocumentFolderPO(submissionId, folderVO));
            }

            updateRhinCodingBatch(folderVO, DocumentFolderVO::getId, DocumentFolderVO::getTypeCode);
        }

        List<Long> ids = folders.stream().map(DocumentFolderVO::getId).collect(Collectors.toList());
        List<Long> removeIds = list.stream().filter(o -> !ids.contains(o.getId())).map(DocumentFolderPO::getId).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(removeIds)) {
            documentFolderMapper.deleteBatchIds(removeIds);
        }
    }

    private void updateAssociation(DocumentSubmissionDO dto, long submissionId) {
        List<DocumentAssociationVO> associations = dto.getAssociations();
        if (CollectionUtils.isEmpty(associations)) {
            return;
        }

        QueryWrapper<DocumentAssociationPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(DocumentAssociationPO::getSubmissionId, submissionId);
        List<DocumentAssociationPO> list = documentAssociationMapper.selectList(queryWrapper);

        for (DocumentAssociationVO associationVO : associations) {
            DocumentAssociationPO po = list.stream().filter(o -> o.getId().equals(associationVO.getId())).findFirst().orElse(null);
            if (po != null) {
                documentAssociationMapper.updateById(new DocumentAssociationPO(submissionId, associationVO));
            } else {
                associationVO.setId(uidWorker.nextId());
                documentAssociationMapper.insert(new DocumentAssociationPO(submissionId, associationVO));
            }
        }

        List<Long> ids = associations.stream().map(DocumentAssociationVO::getId).collect(Collectors.toList());
        List<Long> removeIds = list.stream().filter(o -> !ids.contains(o.getId())).map(DocumentAssociationPO::getId).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(removeIds)) {
            documentAssociationMapper.deleteBatchIds(removeIds);
        }
    }

    private <T> void updateRhinCoding(T entity, SFunction<T, Long> idGetter, SFunction<T, RhinCodeableVO> rhinCodingGetter) {
        String module = getPropertyName(entity, rhinCodingGetter);
        RhinCodeableVO rhinCodeableVO = rhinCodingGetter.apply(entity);
        updateRhinCodingList(rhinCodeableVO, idGetter.apply(entity), module);
    }

    private <T> void updateRhinCodingBatch(T entity, SFunction<T, Long> idGetter, SFunction<T, List<RhinCodeableVO>> rhinCodingGetter) {
        String module = getPropertyName(entity, rhinCodingGetter);
        List<RhinCodeableVO> voList = rhinCodingGetter.apply(entity);
        for (RhinCodeableVO rhinCodeableVO : voList) {
            updateRhinCodingList(rhinCodeableVO, idGetter.apply(entity), module);
        }
    }

    private <T> void updateRhinCodingSingle(T entity, SFunction<T, Long> idGetter, SFunction<T, RhinCodingVO> rhinCodingGetter) {
        String module = getPropertyName(entity, rhinCodingGetter);
        RhinCodingVO rhinCodingVO = rhinCodingGetter.apply(entity);
        Long moduleId = idGetter.apply(entity);
        if (Objects.nonNull(rhinCodingVO)) {
            RhinCodingPO po = new RhinCodingPO(moduleId, module, rhinCodingVO);

            QueryWrapper<RhinCodingPO> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(RhinCodingPO::getModuleId, moduleId).eq(RhinCodingPO::getModule, module);
            RhinCodingPO rhinCodingPO = rhinCodingMapper.selectOne(queryWrapper);
            if (rhinCodingPO == null) {
                po.setId(uidWorker.nextId());
                rhinCodingMapper.insert(po);
            } else {
                po.setId(rhinCodingPO.getId());
                rhinCodingMapper.updateById(po);
            }
        }
    }

    private void updateRhinCodingList(RhinCodeableVO rhinCodeableVO, Long moduleId, String module) {
        List<RhinCodingVO> rhinCodingList = rhinCodeableVO.getCoding();
        if (!CollectionUtils.isEmpty(rhinCodingList)) {
            for (RhinCodingVO rhinCodingVO : rhinCodingList) {
                RhinCodingPO po = new RhinCodingPO(moduleId, module, rhinCodingVO);

                QueryWrapper<RhinCodingPO> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().eq(RhinCodingPO::getModuleId, moduleId).eq(RhinCodingPO::getModule, module);
                RhinCodingPO rhinCodingPO = rhinCodingMapper.selectOne(queryWrapper);
                if (rhinCodingPO == null) {
                    po.setId(uidWorker.nextId());
                    rhinCodingMapper.insert(po);
                } else {
                    po.setId(rhinCodingPO.getId());
                    rhinCodingMapper.updateById(po);
                }
            }
        }
    }

}
